package org.apache.sling.webresource.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.nodetype.NodeType; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.query.Row; import javax.jcr.query.RowIterator; import org.apache.commons.collections.MapUtils; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.SlingConstants; import org.apache.sling.jcr.api.SlingRepository; import org.apache.sling.webresource.WebResourceInventoryManager; import org.apache.sling.webresource.model.WebResourceGroup; import org.apache.sling.webresource.util.JCRUtils; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.ComponentContext; import org.osgi.service.event.EventAdmin; import org.osgi.service.event.EventConstants; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component(metatype = true, immediate = true) @Service public class WebResourceInventoryManagerImpl implements WebResourceInventoryManager { @Reference private SlingRepository repository; @Reference private EventAdmin eventAdmin; private Session adminSession; private BundleContext bundleContext; private Map<String, ServiceRegistration> webResourceServiceRegistration; private final Logger log = LoggerFactory.getLogger(getClass()); private Map<String, Map<String, List<String>>> webResourceExtentionInventoryMap; private Map<String, String> webResourceNamePathMap; protected void activate(ComponentContext context) { bundleContext = context.getBundleContext(); webResourceServiceRegistration = new HashMap<String, ServiceRegistration>(); webResourceExtentionInventoryMap = new HashMap<String, Map<String, List<String>>>(); webResourceNamePathMap = new HashMap<String, String>(); try { adminSession = repository.loginAdministrative(null); registerWebResourceGroupFolderHandler(); webResourceNamePathMap.putAll(getWebResources(adminSession)); for (Entry<String, String> currentWebResourceEntry : webResourceNamePathMap .entrySet()) { String webResourceGroupName = currentWebResourceEntry.getKey(); buildInventory(webResourceGroupName); registerWebResourceGroupListener(webResourceGroupName, currentWebResourceEntry.getValue()); } } catch (RepositoryException e) { log.error("Could not Login to admin session", e); } registerWebResourceGroupEventHandler(); } private void registerWebResourceGroupEventHandler() { String[] topics = { TOPIC_WEB_RESOURCE_CREATED, TOPIC_WEB_RESOURCE_DELETED }; Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(EventConstants.EVENT_TOPIC, topics); bundleContext.registerService(EventHandler.class.getName(), new WebResourceGroupEventHandler(), props); } private void registerWebResourceGroupFolderHandler() { String[] allSlingResourceTopics = { SlingConstants.TOPIC_RESOURCE_ADDED, SlingConstants.TOPIC_RESOURCE_REMOVED }; Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(EventConstants.EVENT_TOPIC, allSlingResourceTopics); bundleContext.registerService(EventHandler.class.getName(), new WebResourceGroupFolderHandler(), props); } public Map<String, String> getWebResources(Session session) throws RepositoryException { Map<String, String> result = new HashMap<String, String>(); Query query = session .getWorkspace() .getQueryManager() .createQuery( "SELECT * FROM [webresource:WebResourceGroup] as webResourceGroupSet", Query.JCR_SQL2); QueryResult queryResult = query.execute(); NodeIterator queryIt = queryResult.getNodes(); while (queryIt.hasNext()) { Node webResourceNode = queryIt.nextNode(); result.put(webResourceNode.getProperty(WebResourceGroup.NAME) .getString(), webResourceNode.getPath()); } return result; } private void unregisterWebResourceGroupListener(final String webResourceName) throws RepositoryException { ServiceRegistration serviceRegistration = webResourceServiceRegistration .get(webResourceName); serviceRegistration.unregister(); } private void registerWebResourceGroupListener(final String webResourceName, final String webResourcePath) throws RepositoryException { if (!webResourceServiceRegistration.containsKey(webResourceName)) { String[] topics = new String[] { SlingConstants.TOPIC_RESOURCE_ADDED, SlingConstants.TOPIC_RESOURCE_CHANGED, SlingConstants.TOPIC_RESOURCE_REMOVED }; Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put(EventConstants.EVENT_TOPIC, topics); props.put(EventConstants.EVENT_FILTER, "(path=" + webResourcePath + "/*)"); this.webResourceServiceRegistration.put(webResourceName, bundleContext.registerService(EventHandler.class.getName(), new InventoryEventHandler(), props)); log.info("Registered Inventory Event Handler for " + webResourceName); } } /** * * Runs Query for Web Resource Nodes in a given group * * @param session * @param webResourceGroupName * @return * @throws RepositoryException */ protected QueryResult getWebResourceGroupQueryResults(Session session, String webResourceGroupName) throws RepositoryException { Query query = session .getWorkspace() .getQueryManager() .createQuery( "SELECT * FROM [nt:file] INNER JOIN [webresource:WebResourceGroup] as webResourceGroupSet ON ISDESCENDANTNODE([nt:file], webResourceGroupSet) WHERE webResourceGroupSet.[webresource:name] = $webResourceName", Query.JCR_SQL2); query.bindValue("webResourceName", session.getValueFactory() .createValue(webResourceGroupName)); QueryResult queryResult = query.execute(); return queryResult; } private void buildInventory(String webResourceGroupName) throws RepositoryException { log.info("Creating Inventory for Web Resource Group: " + webResourceGroupName); QueryResult result = getWebResourceGroupQueryResults(adminSession, webResourceGroupName); RowIterator rowIterator = result.getRows(); while (rowIterator.hasNext()) { Row currentRow = rowIterator.nextRow(); Node currentFileNode = currentRow.getNode("nt:file"); String currentPath = currentFileNode.getPath(); String currentExtention = JCRUtils .getNodeExtension(currentFileNode); if (!(currentExtention.equals("js") || currentExtention .equals("css"))) { updateWebResourceExtensionInventory(currentPath, currentFileNode); } } log.info("Created Inventory for Web Resource Group: " + webResourceGroupName); if (log.isDebugEnabled() && MapUtils.isNotEmpty(webResourceExtentionInventoryMap .get(webResourceGroupName))) { for (Entry<String, List<String>> extentionListEntry : webResourceExtentionInventoryMap .get(webResourceGroupName).entrySet()) { log.debug("Extension: " + extentionListEntry.getKey() + " Items: " + extentionListEntry.getValue()); } } Dictionary<String, Object> properties = new Hashtable<String, Object>(); properties.put("paths", Collections.singletonList(webResourceNamePathMap .get(webResourceGroupName))); org.osgi.service.event.Event compileEvent = new org.osgi.service.event.Event( WebResourceInventoryManager.COMPILE_EVENT, properties); eventAdmin.postEvent(compileEvent); } private String removePathFromWebResourceExtensionInventory(String path) throws RepositoryException { String webResourceGroupName = null; for (Entry<String, Map<String, List<String>>> currentWebResourceExtensionEntry : webResourceExtentionInventoryMap .entrySet()) { for (Entry<String, List<String>> currentExtensionEntry : currentWebResourceExtensionEntry .getValue().entrySet()) { if (currentExtensionEntry.getValue().remove(path)) { webResourceGroupName = currentWebResourceExtensionEntry .getKey(); } } } return webResourceGroupName; } public String getWebResourceGroupForNode(Node childNode) throws RepositoryException { if (childNode.isNodeType(WebResourceGroup.NODE_TYPE)) { return childNode.getProperty(WebResourceGroup.NAME).getString(); } int depth = childNode.getDepth(); if (depth > 0) { return getWebResourceGroupForNode(childNode.getParent()); } else { throw new ItemNotFoundException(); } } private void updateWebResourceExtensionInventory(String path, Node resourceNode) throws RepositoryException { String webResourceGroupName = getWebResourceGroupForNode(resourceNode); Map<String, List<String>> extentionInventoryMap = webResourceExtentionInventoryMap .get(webResourceGroupName); if (extentionInventoryMap == null) { extentionInventoryMap = new HashMap<String, List<String>>(); webResourceExtentionInventoryMap.put(webResourceGroupName, extentionInventoryMap); } String extension = JCRUtils.getNodeExtension(resourceNode); List<String> extensionInventoryList = extentionInventoryMap .get(extension); if (extensionInventoryList == null) { extensionInventoryList = new ArrayList<String>(); extentionInventoryMap.put(extension, extensionInventoryList); } extensionInventoryList.add(path); } class InventoryEventHandler implements EventHandler { @Override public void handleEvent(org.osgi.service.event.Event event) { String eventTopic = event.getTopic(); String path = (String) event.getProperty("path"); String webResourceGroupPath = null; try { if (eventTopic.equals(SlingConstants.TOPIC_RESOURCE_ADDED) || eventTopic .equals(SlingConstants.TOPIC_RESOURCE_CHANGED)) { Node resourceNode = adminSession.getNode(path); if (!ignoreInventoryEvent(event, resourceNode)) { log.info("Update Inventory for Web Resource Path: " + path); updateWebResourceExtensionInventory(path, resourceNode); String webResourceGroupName = getWebResourceGroupForNode(resourceNode); webResourceGroupPath = webResourceNamePathMap .get(webResourceGroupName); } } else if (eventTopic .equals(SlingConstants.TOPIC_RESOURCE_REMOVED)) { if (!ignoreInventoryEvent(event, null)) { log.info("Remove Inventory for Web Resource Path: " + path); String webResourceGroupName = removePathFromWebResourceExtensionInventory(path); webResourceGroupPath = webResourceNamePathMap .get(webResourceGroupName); } } if (webResourceGroupPath != null) { Dictionary<String, Object> properties = new Hashtable<String, Object>(); properties.put("paths", Collections.singletonList(webResourceGroupPath)); org.osgi.service.event.Event compileEvent = new org.osgi.service.event.Event( WebResourceInventoryManager.COMPILE_EVENT, properties); eventAdmin.postEvent(compileEvent); } } catch (RepositoryException e) { log.error("Error Detecting Web Resource Event", e); } } private boolean ignoreInventoryEvent( org.osgi.service.event.Event event, Node resourceNode) throws RepositoryException { boolean skipSweep = false; String path = (String) event.getProperty("path"); // We don't want to trigger a sweep if only the inventory was changed // Only if the inventory was deleted. if ((path.endsWith(WebResourceGroup.INVENTORY) && !SlingConstants.TOPIC_RESOURCE_REMOVED.equals(event.getTopic())) || path.endsWith(".css") || path.endsWith(".js")) { skipSweep = true; } else { if (resourceNode != null && !resourceNode.isNodeType(NodeType.NT_FILE)) { skipSweep = true; } } return skipSweep; } } class WebResourceGroupFolderHandler implements EventHandler { @Override public void handleEvent(org.osgi.service.event.Event event) { try { String path = (String) event.getProperty("path"); String eventTopic = event.getTopic(); Dictionary<String, Object> properties = new Hashtable<String, Object>(); properties.put("path", path); org.osgi.service.event.Event osgiEvent = null; if (eventTopic.equals(SlingConstants.TOPIC_RESOURCE_ADDED)) { Node addedNode = adminSession.getNode(path); if (addedNode.isNodeType(WebResourceGroup.NODE_TYPE)) { log.info("Web Resource Add/Move for path: " + path); osgiEvent = new org.osgi.service.event.Event( TOPIC_WEB_RESOURCE_CREATED, properties); } } else { for (Entry<String, String> webResourceNamePathEntry : webResourceNamePathMap .entrySet()) { if (webResourceNamePathEntry.getValue().equals(path)) { log.info("Web Resource Delete for path: " + path); properties.put(WebResourceGroup.NAME, webResourceNamePathEntry.getKey()); osgiEvent = new org.osgi.service.event.Event( TOPIC_WEB_RESOURCE_DELETED, properties); } } } if (osgiEvent != null) { eventAdmin.postEvent(osgiEvent); } } catch (RepositoryException e) { log.error("Error on JCR event listener", e); } } } class WebResourceGroupEventHandler implements EventHandler { @Override public void handleEvent(org.osgi.service.event.Event event) { String eventTopic = event.getTopic(); String path = (String) event.getProperty("path"); try { if (eventTopic.equals(TOPIC_WEB_RESOURCE_CREATED)) { Node webResourceNode = adminSession.getNode(path); String webResourceNodeName = webResourceNode.getProperty( WebResourceGroup.NAME).getString(); webResourceNamePathMap.put(webResourceNodeName, path); registerWebResourceGroupListener(webResourceNodeName, path); buildInventory(webResourceNodeName); } else if (eventTopic.equals(TOPIC_WEB_RESOURCE_DELETED)) { String webResourceNodeName = (String) event .getProperty(WebResourceGroup.NAME); webResourceNamePathMap.remove(webResourceNodeName); unregisterWebResourceGroupListener(webResourceNodeName); } } catch (RepositoryException e) { log.error("Could not process Web Resource Update: " + eventTopic + " at: " + path, e); } } } @Override public List<String> getSourceWebResources(String webResourceName) { List<String> result = new ArrayList<String>(); Map<String, List<String>> extentionMap = webResourceExtentionInventoryMap .get(webResourceName); for (List<String> currentResourceList : extentionMap.values()) { result.addAll(currentResourceList); } return result; } @Override public String getWebResourcePathLookup(String webResourceName) { return webResourceNamePathMap.get(webResourceName); } @Override public Set<String> getAllWebResourceNames() { return this.webResourceNamePathMap.keySet(); } @Override public Collection<String> getAllWebResourcePaths() { return this.webResourceNamePathMap.values(); } protected void deactivate(ComponentContext context) { if (adminSession != null) { adminSession.logout(); } } }